// File: jmcomic-core/src/main/java/dev/jukomu/common/postman/proxy/MultiPartPostman.java
package dev.jukomu.common.postman.proxy;

import dev.jukomu.common.postman.Postman;
import dev.jukomu.common.util.net.IResponse;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okio.BufferedSink;
import okio.Okio;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

/**
 * 对应Python的MultiPartPostman，一个Postman代理，用于处理Multipart请求。
 * 它可以自动设置Content-Type为multipart/form-data。
 */
public class MultiPartPostman extends PostmanProxy {

    /**
     * 构造函数。
     *
     * @param postman 被代理的Postman实例。
     */
    public MultiPartPostman(Postman postman) {
        super(postman);
    }

    @Override
    public IResponse post(String url, Map<String, Object> kwargs) throws Exception {
        Map<String, Object> actualKwargs = kwargs != null ? new java.util.HashMap<>(kwargs) : new java.util.HashMap<>();
        Object data = actualKwargs.get("data");

        if (data instanceof MultipartEncoderMonitor) { // If it's our custom MultipartEncoderMonitor
            MultipartEncoderMonitor monitor = (MultipartEncoderMonitor) data;
            // Build MultipartBody from the monitor's fields
            MultipartBody body = buildMultipartBody(monitor.getFields());
            // Wrap the body with a progress listener if callback is set
            RequestBody monitoredBody = new CountingRequestBody(body, monitor.getCallback());

            actualKwargs.put("data", monitoredBody); // Replace data with OkHttp RequestBody

            // Set Content-Type header (OkHttp handles multipart Content-Type header automatically for MultipartBody)
            // But if the Python code explicitly set it, we might need to preserve it.
            // Python's `build_headers` sets Content-Type based on `data.content_type`.
            // OkHttp's MultipartBody will generate the correct Content-Type including boundary.
            // If the user explicitly sets Content-Type in headers, it might conflict.
            // For now, let OkHttp handle it. If explicit override is needed, add headers logic here.
        } else if (data instanceof Map && isLikelyMultipart((Map<String, Object>) data)) {
            // If it's a Map that looks like files or multipart fields, convert it.
            // This is a heuristic. Python's requests library is more flexible.
            MultipartBody body = buildMultipartBody((Map<String, Object>) data);
            actualKwargs.put("data", body);
        }
        // else: data is not multipart, let default post handle it.

        return super.post(url, actualKwargs);
    }

    @Override
    public Postman copy() {
        return new MultiPartPostman(postman.copy());
    }

    /**
     * 检查一个Map是否可能是Multipart数据。
     * 简单的启发式检查，如果Map中包含值为File或byte[]的条目，则可能是Multipart。
     *
     * @param data Map数据
     * @return 如果是Multipart数据返回 true
     */
    private boolean isLikelyMultipart(Map<String, Object> data) {
        for (Object value : data.values()) {
            if (value instanceof File || value instanceof byte[]) {
                return true;
            }
            // Python's files parameter can be a tuple (filename, file-like object, content_type)
            // Need to support this tuple format.
            if (value instanceof Object[]) { // Check if it's a tuple-like object
                Object[] tuple = (Object[]) value;
                if (tuple.length >= 2 && (tuple[1] instanceof File || tuple[1] instanceof byte[] || tuple[1] instanceof java.io.InputStream)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 从Python风格的字段Map构建OkHttp的MultipartBody。
     * Python的fields格式：
     * {'name': value}
     * {'file_field': ('filename', file_object, 'content_type')}
     *
     * @param fields Python风格的字段Map
     * @return OkHttp MultipartBody
     * @throws IOException 如果文件读取失败
     */
    private MultipartBody buildMultipartBody(Map<String, Object> fields) throws IOException {
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);

        for (Map.Entry<String, Object> entry : fields.entrySet()) {
            String name = entry.getKey();
            Object value = entry.getValue();

            if (value instanceof Object[]) { // (filename, file_object, content_type) tuple
                Object[] fileTuple = (Object[]) value;
                String filename = (String) fileTuple[0];
                Object fileContent = fileTuple[1];
                String contentType = fileTuple.length > 2 ? (String) fileTuple[2] : null;

                RequestBody fileRequestBody = null;
                if (fileContent instanceof File) {
                    fileRequestBody = RequestBody.create((File) fileContent, MediaType.parse(contentType != null ? contentType : "application/octet-stream"));
                } else if (fileContent instanceof byte[]) {
                    fileRequestBody = RequestBody.create((byte[]) fileContent, MediaType.parse(contentType != null ? contentType : "application/octet-stream"));
                } else if (fileContent instanceof java.io.InputStream) {
                    // Create RequestBody from InputStream (needs to be streamed)
                    // OkHttp doesn't have a direct RequestBody.create(InputStream).
                    // This requires a custom RequestBody or converting to byte array.
                    // For simplicity, converting to byte array. For large files, use streaming.
                    fileRequestBody = RequestBody.create(Okio.buffer(Okio.source((java.io.InputStream) fileContent)).readByteArray(), MediaType.parse(contentType != null ? contentType : "application/octet-stream"));
                }

                if (fileRequestBody != null) {
                    builder.addFormDataPart(name, filename, fileRequestBody);
                } else {
                    System.err.println("Warning: Unsupported file content type in multipart: " + fileContent.getClass().getName());
                }
            } else { // Regular form field
                builder.addFormDataPart(name, String.valueOf(value));
            }
        }
        return builder.build();
    }

    /**
     * 对应Python的requests_toolbelt.MultipartEncoderMonitor。
     * 这是一个自定义的RequestBody，用于监听上传进度。
     */
    public static class MultipartEncoderMonitor {
        private final Map<String, Object> fields; // 原始字段Map
        private final UploadProgressCallback callback; // 进度回调函数

        // Python's MultipartEncoderMonitor has bytes_read, len, etc.
        // We will pass totalBytes and bytesWritten to the callback.
        // This is a simple wrapper for the fields and callback, not the OkHttp RequestBody itself.

        public MultipartEncoderMonitor(Map<String, Object> fields, UploadProgressCallback callback) {
            this.fields = Objects.requireNonNull(fields, "Fields cannot be null.");
            this.callback = callback; // Callback can be null
        }

        public Map<String, Object> getFields() {
            return fields;
        }

        public UploadProgressCallback getCallback() {
            return callback;
        }

        // Python's from_fields method
        public static MultipartEncoderMonitor fromFields(Map<String, Object> fields, UploadProgressCallback callback) {
            return new MultipartEncoderMonitor(fields, callback);
        }
    }

    /**
     * 上传进度回调接口。
     * 对应Python回调中传递的bytes_read和len。
     */
    @FunctionalInterface
    public interface UploadProgressCallback {
        void onProgress(long bytesWritten, long totalBytes);
    }

    /**
     * OkHttp自定义RequestBody，用于报告上传进度。
     */
    private static class CountingRequestBody extends RequestBody {
        private final RequestBody delegate;
        private final UploadProgressCallback callback;

        public CountingRequestBody(RequestBody delegate, UploadProgressCallback callback) {
            this.delegate = delegate;
            this.callback = callback;
        }

        @Override
        public MediaType contentType() {
            return delegate.contentType();
        }

        @Override
        public long contentLength() throws IOException {
            return delegate.contentLength();
        }

        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            CountingSink countingSink = new CountingSink(sink, contentLength(), callback);
            BufferedSink bufferedSink = Okio.buffer(countingSink);
            delegate.writeTo(bufferedSink);
            bufferedSink.flush(); // Ensure all data is written
        }

        private static final class CountingSink extends okio.ForwardingSink {
            private long bytesWritten = 0L;
            private final long contentLength;
            private final UploadProgressCallback callback;

            CountingSink(okio.Sink delegate, long contentLength, UploadProgressCallback callback) {
                super(delegate);
                this.contentLength = contentLength;
                this.callback = callback;
            }

            @Override
            public void write(okio.Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                bytesWritten += byteCount;
                if (callback != null) {
                    callback.onProgress(bytesWritten, contentLength);
                }
            }
        }
    }
}